package com.onpositive.dataminer.core;

import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import com.onpositive.data.IPropertyConstants;
import com.onpositive.dataminer.userstorage.UserProperty;
import com.onpositive.dataminer.userstorage.UserValueStorage;
import com.onpositive.dataminer.value.transformers.WikiLinksRedirectionTransformer;
import com.onpositive.knowledge.model.IAuditedUniverse;
import com.onpositive.knowledge.model.IThing;
import com.onpositive.knowledge.model.IUniverse;
import com.onpositive.knowledge.model.inference.InferTransformer;
import com.onpositive.knowledge.model.value.audits.AbstractAuditResult;
import com.onpositive.knowledge.model.value.audits.DisambiguationValueAudit;
import com.onpositive.knowledge.model.value.audits.GeneralAuditResult;
import com.onpositive.knowledge.model.value.audits.IAudit;
import com.onpositive.knowledge.model.value.audits.IAuditManager;
import com.onpositive.knowledge.model.value.audits.IUniverseAudit;
import com.onpositive.knowledge.model.value.audits.IValueAudit;
import com.onpositive.knowledge.model.value.audits.ReferenceResolvingAudit;
import com.onpositive.knowledge.model.values.ActualDocumentsTransformer;
import com.onpositive.knowledge.model.values.DescribedValue;
import com.onpositive.knowledge.model.values.IHasAuditResult;
import com.onpositive.knowledge.model.values.IValueTransformer;
import com.onpositive.knowledge.model.values.IdentPropertyTransformer;
import com.onpositive.knowledge.model.values.IdentValueSplitterTransformer;
import com.onpositive.knowledge.model.values.MergeIdenticalTransformer;
import com.onpositive.knowledge.model.values.PredictionTransformer;
import com.onpositive.knowledge.model.values.ReplacementTransformer;
import com.onpositive.knowledge.model.values.StatisticValueTransformer;
import com.onpositive.knowledge.model.values.WikiLinksRefiningTransformer;
import com.onpositive.semantic.model.api.changes.IValueListener;
import com.onpositive.semantic.model.api.meta.DefaultMetaKeys;
import com.onpositive.semantic.model.api.property.IProperty;
import com.onpositive.semantic.model.api.property.PropertyAccess;
import com.onpositive.semantic.model.api.realm.IRealm;
import com.onpositive.units.ParsedValue;
import com.onpositive.utils.DataLocationProvider;
import com.onpositive.utils.ObjectSerializer;

public class AuditManager implements IAuditManager {
	
	private static final String MAIN_SETTINGS_FILENAME = "main-settings.dat";
	private static final String MAIN_RESULT_FILENAME = "main-result.dat";
	private static final String AUDIT_SUBPATH = "AuditResults/";
	private static final String TRANSFORMER_SAMPLE_AND_REFERENCE_VALUES_SUBPATH = "TransformerValues/";
	/**
	 * 
	 */
	private static final long serialVersionUID = 2477051857583991345L;
	public static final String ID = "audit-manager";

	public AuditManager( IUniverse universe ) {
		this.universe = universe ;
		loadAuditsAndtransformers() ;
		initTransformers() ;
		loadAuditResult() ;
		loadSampleAndReferenceValues() ;
	}
	

	IUniverse universe ;
	HashMap<String,IAudit>  auditSet = new HashMap<String, IAudit>() ;
	HashMap<String,IValueTransformer>  transformersSet = new HashMap<String, IValueTransformer>() ;
	ArrayList<ObjEntry<IAudit>> auditQueue = new ArrayList<ObjEntry<IAudit>>() ;
	ArrayList<ObjEntry<IValueTransformer>> transformerQueue = new ArrayList<ObjEntry<IValueTransformer>>() ;
	AbstractAuditResult mainResult = new GeneralAuditResult( null );
	
	
	public void initTransformers()
	{
		if( transformerQueue.size() != 0 )
			return ;
		
		addTransformer( new InferTransformer() );
		
		if( getUniverse() instanceof IAuditedUniverse )
		{
			addTransformer( new ActualDocumentsTransformer(this) );
			addTransformer( new ReplacementTransformer(this,ReferenceResolvingAudit.ID),ReferenceResolvingAudit.ID ) ;
			addTransformer( new WikiLinksRefiningTransformer(this) );			
			addTransformer( new ReplacementTransformer(this,DisambiguationValueAudit.ID),DisambiguationValueAudit.ID ) ;
			addTransformer( new WikiLinksRedirectionTransformer(this) );
			addTransformer( new IdentPropertyTransformer(this) );
			addTransformer( new IdentValueSplitterTransformer(this) );
			//addTransformer( new ValueSequenceOrderingTransformer(this) );
			addTransformer( new StatisticValueTransformer(this) ) ;
			addTransformer( new PredictionTransformer(this) );
		}		
		addTransformer( new MergeIdenticalTransformer() );		
	}
	
	private void addTransformer( IValueTransformer vt, Object... cArgs  )
	{
		Iterable<IProperty> properties = universe.getProperties(null);
		int order = transformerQueue.size() ;
		ObjEntry<IValueTransformer> objEntry = new ObjEntry<IValueTransformer>( vt, order ,properties, this, cArgs);
		objEntry.getCommonSettings().objectActive = true ;
		final String transformerId = vt.getId();
		objEntry.setID( transformerId ) ;
		transformerQueue.add( objEntry );
		transformersSet.put( transformerId, vt) ;
	}

	
	
	public AbstractAuditResult createAuditResult(){
		return mainResult;
	}
	
	public AbstractAuditResult getAuditResult(){
		return mainResult;
	}
	
	protected void loadAuditResult() {
		
		final File dirFile = getDataLocation(AUDIT_SUBPATH);
		if( !dirFile.exists() )
			return ;
				
		final File resultFile = new File( dirFile, MAIN_RESULT_FILENAME );
		if( !resultFile.exists() )
			return ;
		
		AbstractAuditResult newResult = (AbstractAuditResult) ObjectSerializer.deserialize(	resultFile );			
				
		if( newResult != null ){
			newResult.setUniverse(universe) ;
			mainResult = newResult ;			
		}
	}
	
	public void saveAuditResult(){
		
		final File dirFile = getDataLocation(AUDIT_SUBPATH);
		dirFile.mkdirs() ;		
		final File resultFile = new File( dirFile, MAIN_RESULT_FILENAME );		
		ObjectSerializer.serialize( mainResult, resultFile );
	}	
	
	@Override
	public void addAudit( IAudit audit )
	{
		String id = audit.getId() ;
		if( auditSet.get(id) != null )
			return ;
		
		audit.setResultSource( this ) ;
		audit.setUniverse(getUniverse());
		auditSet.put(id, audit) ;
		
		ObjEntry<IAudit> objEntry = new ObjEntry<IAudit>(
				audit,
				auditQueue.size(),
				universe.getProperties(null),
				this);
		
		objEntry.setID( audit.getId() ) ;
		auditQueue.add( objEntry ) ;		
	}
	
	@Override
	public Set<String> getRequiredAudits() {
		return new HashSet<String>() ;
	}

	@Override
	public void perform( Iterable<IProperty> _properties, Iterable<Object> objects, AbstractAuditResult auditResult )
	{		
		if( !( auditResult instanceof GeneralAuditResult ) )
			return ;		
		
		Iterable<IProperty> properties = _properties != null ? _properties : getUniverse().getProperties( null ) ;		
		GeneralAuditResult rd=(GeneralAuditResult) auditResult;
		
		
		for (IProperty m:properties)
		{
			//FIXME
			if(m.getId().equals(IPropertyConstants.EXTERNAL_DEFAULT_PICTURE_PROPERTY_NAME)){
				continue;
			}
			IValueAudit valueAudit = DefaultMetaKeys.getService(m,IValueAudit.class);
			if (valueAudit!=null)
				applyAudit(valueAudit, m, rd, objects);
		}
		
		
		HashMap<IProperty,GeneralAuditResult> resMap = new HashMap<IProperty, GeneralAuditResult>() ;		
		for ( ObjEntry<IAudit> ae : this.auditQueue ) {
			
			if( !ae.getCommonSettings().isActive() )
				continue ;
			
			IAudit a = ae.getObject() ;
			
			if( a instanceof IValueAudit ){
				IValueAudit valueAudit = (IValueAudit)a;
				for( IProperty prop : properties ){
					
					ObjSettings as = ae.getPropertySpecificSettings( prop.getId() ) ;
					if( as != null && !as.isActive() )
						continue ;					

					AbstractAuditResult perform = valueAudit.createAuditResult(prop) ;
					valueAudit.perform( prop, objects, perform);
					if (perform != null && perform.isLooksApplyable()) {
						
						GeneralAuditResult r = resMap.get( prop ) ;
						if( r == null ){
							r = new GeneralAuditResult( prop );
							r.setLooksApplyable( false );
							resMap.put(prop, r) ;
							rd.addChildAudit( r ) ;
							rd.setLooksApplyable(true) ;
						}
						
						for( Object o : perform.getSuspicious() )
							r.addAsSuspicious( o );
						
						r.addChildAudit(perform);
						r.setLooksApplyable( true );
					}
				}
			}
			if( a instanceof IUniverseAudit ){
				IUniverseAudit universeAudit = (IUniverseAudit)a;
				AbstractAuditResult perform = universeAudit.createAuditResult() ;
				universeAudit.perform(properties, objects,perform);
				if (perform != null && perform.isLooksApplyable() )
				{
					rd.addChildAudit(perform)					 ;
					rd.setLooksApplyable(true) ;
				}				
			}
		}
		
	}
	
	protected void applyAudit(IValueAudit valueAudit, IProperty m,	GeneralAuditResult rd, Iterable<Object> objects)
	{
		AbstractAuditResult auditResult = valueAudit.createAuditResult(m) ;
		if( auditResult == null )
			return ;
		
		valueAudit.perform(m, objects, auditResult);		
		
		if (auditResult.isLooksApplyable()){
			rd.addChildAudit(auditResult);
			rd.setLooksApplyable(true);
		}
	}
	
	protected void applyAudit( IUniverseAudit universeAudit, Iterable<IProperty> m, GeneralAuditResult rd, Iterable<Object> objects)
	{
		AbstractAuditResult auditResult = universeAudit.createAuditResult() ;
		rd.addChildAudit(auditResult);
		universeAudit.perform(m, objects, auditResult);
		if( auditResult == null )
			return ;
		
		if (!auditResult.isLooksApplyable())			
			rd.removeChildAudit(auditResult) ;
		else
			rd.setLooksApplyable(true);
	}

	public IUniverse getUniverse() {
		return universe;
	}

	@Override
	public String getId() {
		return ID;
	}
	
	public ArrayList<ObjEntry<IAudit>> getAuditQueue() {
		return auditQueue;
	}

	public ArrayList<ObjEntry<IValueTransformer>> getTransformerQueue() {
		return transformerQueue;
	}

	public void setTransformerQueue( ArrayList<IValueTransformer> transformerList) {

		transformerQueue.clear() ;
		for( IValueTransformer vt : transformerList )
			addTransformer(vt) ;			
	}
	
	public DescribedValue[] adjust( Object object, IProperty prop, DescribedValue... original )
	{
		for ( ObjEntry<IValueTransformer> oe : transformerQueue ) {
			if( !oe.getCommonSettings().isActive() )
				continue ;
			
			String propId = prop.getId();
			if(propId==null)
				continue;
			
			if(oe.getCommonSettings().isIgnored(propId))
				continue;
			
			original = oe.getObject().adjust(object, prop, original) ;
		}
		return original;
	}

	public static class ObjEntry<T> implements Serializable {
		

		/**
		 * 
		 */
		private static final long serialVersionUID = 2800270270891326853L;

		public ObjEntry( T object, int order, Iterable<IProperty> properties, AuditManager am, Object... cArgs ) {
			super();
			this.object = object;
			this.order = order ;
			this.am = am ;
			this.commonSettings = new ObjSettings( am ) ;
			this.cArgs = cArgs;
			
			if( properties == null )
				return ;
			
			for( IProperty prop : properties )
				getPropertySpecificSettings(prop.getId()).objectActive = true ;
			
		}
		transient private AuditManager am ;
		private int order ;
		private String ID ;
		transient private T object ;		
		private ObjSettings commonSettings ;
		private HashMap<String,ObjSettings> propertySettingsMap = new HashMap<String, AuditManager.ObjSettings>() ;
		private Object[] cArgs;
		
		public T getObject() {
			return object;
		}
		
		public ObjSettings getCommonSettings() {
			return commonSettings;
		}
		
		public ObjSettings getPropertySpecificSettings( String propName )
		{
			ObjSettings objSettings = propertySettingsMap.get(propName);
			if( objSettings == null ){
				objSettings = new ObjSettings( am ) ;
				propertySettingsMap.put(propName, objSettings) ;
			}
			return objSettings ;
		}

		public int getOrder() {
			return order;
		}

		public void setOrder(int order) {
			this.order = order;
		}

		public String getId() {
			return ID;
		}

		public void setID(String iD) {
			ID = iD;
		}
		public AuditManager getAm() {
			return am;
		}

		@SuppressWarnings("unchecked")
		public void setAm(AuditManager am) {
			this.am = am;
			commonSettings.setAm(am) ;
			for(ObjSettings os : propertySettingsMap.values() )
				os.setAm(am) ;
			
			Object obj = DefaultAuditFabric.getAudit(ID) ;
			if( obj == null )				
				obj = DefaultTransformerFactory.getTransformer( ID,(IHasAuditResult) am.getUniverse(), cArgs ) ;
			
			this.object = (T)obj ;
		}
	}
	
	public static class ObjSettings implements Serializable
	{

		/**
		 * 
		 */
		public ObjSettings( AuditManager am )
		{
			this.am = am ;
		}
		private transient AuditManager am ;
		private static final long serialVersionUID = -7730820975322976675L;
		private boolean objectActive = true ;
//		private boolean transformerActive = true ;
		private boolean saveResult = true ;
		
		private HashSet<String> ignoredProperties; 
		
		public boolean isActive() {
			return objectActive;
		}
		public void setActive(boolean auditActive) {
			this.objectActive = auditActive;
			am.saveAuditsAndTransformers() ;
			am.fireChanges();
		}
//		public boolean isTransformerActive() {
//			return transformerActive;
//		}
//		public void setTransformerActive(boolean transformerActive) {
//			this.transformerActive = transformerActive;
//			AuditManager.this.saveAudits() ;
//		}
		public boolean isSaveResult() {
			return saveResult;
		}
		public void setSaveResult(boolean saveResult) {
			this.saveResult = saveResult;
			am.saveAuditsAndTransformers() ;
		}
		
		public AuditManager getAm() {
			return am;
		}
		public void setAm(AuditManager am) {
			this.am = am;
		}
		
		public void setPropertyAsIgnored(String propName, boolean ignore){
			
			if(propName==null)
				return;
			
			if(ignore){
				if(ignoredProperties==null)
					ignoredProperties = new HashSet<String>();				
				
				ignoredProperties.add(propName);
			}
			else if(ignoredProperties!=null){
				ignoredProperties.remove(propName);
			}
			am.saveAuditsAndTransformers() ;
			am.fireChanges();
		}
		
		public boolean isIgnored(String propertyName){
			if(propertyName==null)
				return false;
			
			if(ignoredProperties==null)
				return false;
			
			return ignoredProperties.contains(propertyName);
		}
	
	}
	
	public class InspectionDeltaUnit {
		
		private final Object inputSample ;
		private final Object correctOutput ;
		private final Object actualOutput ;
		
		public InspectionDeltaUnit( Object inputSample, Object correctOutput, Object actualOutput ) {
			super();
			this.inputSample = inputSample;
			this.correctOutput = correctOutput;
			this.actualOutput = actualOutput;
		}

		public Object getInputSample() {
			return inputSample;
		}

		public Object getCorrectOutput() {
			return correctOutput;
		}

		public Object getActualOutput() {
			return actualOutput;
		}
	}
	
	@SuppressWarnings("unchecked")
	protected void loadAuditsAndtransformers() {		
		
		File dirFile = getDataLocation(AUDIT_SUBPATH);
		if( dirFile == null || !dirFile.exists() )
			return ;
		
		File resultFile = new File( dirFile, MAIN_SETTINGS_FILENAME );
		if( !resultFile.exists() )
			return ;
		
		ArrayList<Object> al = (ArrayList<Object>) ObjectSerializer.deserialize(resultFile) ;
		if(al == null)
			return ;
		
		auditQueue = (ArrayList<ObjEntry<IAudit>>)al.get(0) ;
		for( ObjEntry<IAudit> e : auditQueue ){
			e.setAm(this) ;
			e.getObject().setResultSource(this) ;
			auditSet.put(e.getId(), e.getObject() ) ;
		}
		
		transformerQueue = (ArrayList<ObjEntry<IValueTransformer>>)al.get(1) ;
		for( ObjEntry<IValueTransformer> e : transformerQueue ){
			e.setAm(this) ;
			transformersSet.put(e.getId(), e.getObject() ) ;
		}
	}
	
	protected void saveAuditsAndTransformers() {
		
		ArrayList<Object> al = new ArrayList<Object>() ;
		al.add(auditQueue) ;
		al.add(transformerQueue) ;
		
		File dirFile = getDataLocation(AUDIT_SUBPATH);
		if( dirFile == null )
			return;
		
		dirFile.mkdirs() ;
		File resultFile = new File( dirFile, MAIN_SETTINGS_FILENAME );
		
		ObjectSerializer.serialize( al, resultFile );
	}
	
	private HashMap<String,UserValueStorage> inputSampleStorage = new HashMap<String, UserValueStorage>() ; 
	private HashMap<String,UserValueStorage> correctOutputStorage = new HashMap<String, UserValueStorage>() ;
	
	private void loadSampleAndReferenceValues() {
		
		final String smpPostfix = "_smp" ;
		final String refPostfix = "_ref" ;
		File dirFile = getDataLocation( TRANSFORMER_SAMPLE_AND_REFERENCE_VALUES_SUBPATH ) ;
		if( dirFile == null )
			return;
		
		dirFile.mkdirs() ;		
		StringBuilder bld = new StringBuilder() ;
		
		for( ObjEntry<IValueTransformer> te : transformerQueue )
		{
			IValueTransformer t = te.getObject() ;
			String id = t.getId() ;
			
			bld.delete(0, bld.length()) ;			
			bld.append( id ) ;			
			bld.append(smpPostfix) ;
			bld.append(".xml") ;
			String fileName = bld.toString();
			
			File file = new File( dirFile, fileName );				
			UserValueStorage smpStorage = new UserValueStorage( (DefaultUniverse) this.getUniverse() ) ;
			inputSampleStorage.put(id, smpStorage) ;
			smpStorage.setFile(file);
			
			
			bld.delete(id.length(), bld.length()) ;			
			bld.append(refPostfix) ;
			bld.append(".xml") ;
			fileName = bld.toString();
			
			file = new File( dirFile, fileName );				
			UserValueStorage refStorage = new UserValueStorage( (DefaultUniverse) this.getUniverse() ) ;
			correctOutputStorage.put(id, refStorage) ;
			refStorage.setFile(file);
		}
	}
	
	public void addInspectionValue( Object obj, IProperty extProp, String transformerId )
	{		
		UserValueStorage inStorage = inputSampleStorage.get(transformerId) ;
		UserValueStorage outStorage = correctOutputStorage.get(transformerId) ;
		IValueTransformer transf = transformersSet.get( transformerId ) ;
		
		if( inStorage == null || outStorage == null || transf == null )
			return ;
		
		String propId = extProp.getId() ;
		
		UserProperty prop0 = (UserProperty) inStorage.getProperty(null, propId) ;		
		Object value0 = extProp.getValue(obj) ;
		PropertyAccess.setValue(prop0, obj, value0) ;
		
		//ICommand cmd0 = inStorage.getCommandFactory().createCommand(obj, value0, ICommand.SET_VALUE, prop0) ;
		//inStorage.execute(cmd0) ;
		
		UserProperty prop1 = (UserProperty) outStorage.getProperty(null, propId) ;
		DescribedValue value1 = transf.adjust(obj, extProp, (DescribedValue)value0)[0] ;
		PropertyAccess.setValue(prop1, obj, value1) ;
		
		//ICommand cmd1 = outStorage.getCommandFactory().createCommand(obj, value1, ICommand.SET_VALUE, prop1) ;
		//outStorage.execute(cmd1) ;
	}
	
	public ArrayList<InspectionDeltaUnit> getDelta( String transformerId, IProperty prop )
	{
		UserValueStorage inStorage = inputSampleStorage.get(transformerId) ;
		UserValueStorage outStorage = correctOutputStorage.get(transformerId) ;
		IValueTransformer transf = transformersSet.get( transformerId ) ;
		
		if( inStorage == null || outStorage == null || transf == null )
			return null;
		
		String propId = prop.getId() ;
		ArrayList<InspectionDeltaUnit> result = new ArrayList<InspectionDeltaUnit>() ;
		IRealm<IThing> allThings = getUniverse().all() ;
		
		for( IThing thing : allThings )
		{
			Object inputSample = inStorage.getValue(thing, propId) ;
			if( inputSample == null )
				continue ;
			
			Object correctOutput = outStorage.getValue(thing, propId) ;
			if( correctOutput == null )
				continue ;
			
			DescribedValue inputDV = new DescribedValue(inputSample, "") ;
			final DescribedValue actualDV = transf.adjust(thing, prop, inputDV)[0];
			if( actualDV == null || actualDV.value() == null ){
				result.add( new InspectionDeltaUnit( inputSample, correctOutput, new DescribedValue(null, "NULL output")) ) ;
				continue ;
			}
			Object actualOutput = actualDV.value() ;
			
			if( correctOutput.equals(actualOutput) )
				continue ;
			
			result.add( new InspectionDeltaUnit( inputSample, correctOutput, actualOutput) ) ;
		}		
		return result ;
	}

	
	private File getDataLocation( String subPathStr )
	{
		return DataLocationProvider.getDataLocation( subPathStr ) ;		
	}

	@Override
	public void setResultSource(IHasAuditResult source) {}

	@Override
	public IHasAuditResult getResultSource() {
		return this ;
	}

	@Override
	public Class<?>[] getTargetClasses(String auditId) {
		if(auditId.equals(ReferenceResolvingAudit.ID))
			return new Class<?>[]{ParsedValue.class};
		
		return null;
	}

	public void setUniverse(IUniverse universe) {
		this.universe = universe;
	}

	private HashSet<IValueListener<?>> listeners = new HashSet<IValueListener<?>>();
	
	@Override
	public void addValueListener(IValueListener<?> listener) {
		
		if(listener==null)
			return;

		listeners.add(listener);
	}

	@Override
	public void removeValueListener(IValueListener<?> listener) {
		
		if(listener==null)
			return;
		
		listeners.remove(listener);
	}
	
	private void fireChanges(){
		for(IValueListener<?> l : listeners){
			l.valueChanged(null, null);
		}
	}
}
